/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
#ifndef GIMICORE_H_
#define GIMICORE_H_

#include <inttypes.h>
#include "settings.hpp"

#include "gimimessage.h"
#include "multibuffer.h"
#include "gimimessagefilter.h"
#include "gimisubscribers.h"
#include "gimireplyhandler.h"
#include "gimisubscriptions.h"
#include "gimiprotocolhandler.h"
#include "connector.h"

#include "sync.hpp"

namespace gimi
{

class GIMICore : private gim::CSync
{
 private:

  /** This enumeration type is used to map Lock()/Unlock() calls to different purposes.
   *
   */
  enum ESyncObject {
    KSyncObjectServiceDiscoveryBundleArray = 0, ///< Sync object for Subscriber array (Mutex & Cond)
    KSyncObjectUniqueId                    = 1, ///< Unique IDs are read through this mutex.
    KSyncObjectReplyQueue                  = 2
  };


  public:
    GIMICore(int defaultBufferSize,
             const std::string clientDescription,
             const std::string clientLocation,
             const int clientVersion,
             const int clientBuild);
    ~GIMICore();
    
    /**
     * Sets client-description.
     */
    inline void setDescription(const std::string cDescription) {
       clientDescription = cDescription; }
     
    /**
     * Returns current client-description.
     */
    inline std::string getDescription() const { return clientDescription; }
    
    bool readSettings(const gim::CSettings &settings);
    
    /**
     * Return connection status.
     */
    inline bool isConnected() const { return iConnector->isConnected(); }
    
    int connectToHub(const std::string &hubAddress,
                     const int hubPort,
                     const std::string &ownName,
                     const bool aForceNameRegistration,
                     const std::string &aSecretKey,
                     const int aTimeout_ms);
    
    int send(const uint32_t targetId,
             const char *data,
             const int datalen,
             const int majorTypeId,
             const int minorTypeId,
             const int messageId = 0,
             const int messageFlags = 0);
    
    int sendToSubscribers(const char *data,
                          const int datalen,
                          const int majorTypeId,
                          const int minorTypeId,
                          const int messageId = 0,
                          const int messageFlags = 0,
                          const int maxUnsynced = -1,
                          const int timeout = 500);
    


    int serviceDiscovery(t_serviceList &services, const unsigned int clienttimeout = 5000);

    /**
     * Sends servicediscovery to target and waits answer for at most timeoutms
     * before returning.
     *
     * \param[out] servicelist List of services is written here. If list
     * already has content, it will be erased.
     * \param[in] aClientId Identifier of target client.
     * \param[in] aTimeout_ms Timeout in milliseconds.
     * \return GIMI_OK for success, GIMI_TIMEOUT for timeout, GIMI_INTERRUPT
     * when GIMI was stopped while waiting a reply, GIMI_ERROR for other errors.
     */
    int serviceDiscoveryToTarget(t_serviceList &servicelist,
                                 const unsigned int &aClientId,
                                 const unsigned int &aTimeout_ms);
    

    
    int ping(const int clientId,
             const int timeoutms = 5000);
    
    int setTargetDebugLevel(const int clientId,
                            const int debugLevel);
    
    void stop();
    
    /**
     * Gives unique id, thread-safe. Used internally by methods to identify
     * replies for them (ping, servicediscovery etc.)
     * Numbers start from 0 and loop around.
     *
     * \return Returns a new unique id.
     */
    uint32_t giveUid();

    /**
     * Subscription core-method.
     * 
     * \param[in] clientId From whom to subscribe data.
     * \param[in] majorServiceId Type of the service we want to subscribe.
     * \param[in] minorServiceId Instance of the service (usually)
     * \param[in] doUnsubscribe If a subscribe or unsubscribe should be sent.
     * \param[in] timeoutms Time to wait for reply.
     * \return GIMI_OK if subscription succeeded,
     * GIMI_INVALIDTYPE if target was reached but it did not provide specified
     * service,
     * GIMI_TIMEOUT if no reply arrived within timeout milliseconds,
     * GIMI_NOCONNECTION if connection to hub has been lost,
     * GIMI_ERROR if some other error occurred.
     */
    int subscribeCore(const int clientId,
                      const int majorServiceId,
                      const int minorServiceId,
                      bool doUnsubscribe = false,
                      const int timeoutms = 2500);
    
    /**
     * Sends list of services to target client.
     *
     * \return GIMI_OK or GIMI_NOCONNECTION.
     */
    int sendServiceList(const int targetId,
                        const uint32_t msgUid);
    
    /**
     * Processes messagedata in GIMI-format.
     * Reads the first layer of tags in the message and forwards it to
     * processDataMessage or processControlMessage for more processing.
     */
    void handleGimiMessage(const uint8_t *data,
                           const unsigned int dataLength,
                           const uint32_t senderId);


    // Connection
    Connector *iConnector;      ///< Just a simple pointer to previous ;)
    
    // Messagebuffer
    MultiBuffer messageBuffers;

    // Filter
    GIMIMessageFilter messageFilter;

    // Subscribers and services
    GIMISubscribers subscribers;

    // Handles replies to ping queries.
    // Protect with replyMutex
    GIMIReplyHandler replyHandler;
    
    // Handles subscriptions and updating them.
    GIMISubscriptions subscriptions; 
    
  private:
    // Prevents use of copy constructors
    GIMICore(const GIMICore& right);
    const GIMICore& operator= (const GIMICore& right);
    
    /**
     * Tries to find and process servicediscovery-reply from given data.
     * Adds services to serviceList if data was valid.
     */
    int processServiceDiscoveryData(t_serviceList &servicelist,
                                    const int clientId,
                                    const std::string clientName,
                                    const char *data,
                                    const int datalength);
    /**
     * Sends a message received reply to GIMI in the other end.
     * 
     * \return GIMI_OK or GIMI_NOCONNECTION.
     */
    int sendReplyMessage(const int targetId,
                         const int msgDataType,
                         const uint32_t msgUid,
                         const int msgActFlags,
                         const int majorTypeId,
                         const int minorTypeId,
                         const int msgCommand,
                         const int replyValue = GIMI_INVALIDREPLYVALUE);


    /**
     * Upkeep task, sends keepalive-messages to subscribers.
     */
    void upkeepKeepAlive();
    
    /**
     * Sends a keepalive-message to subscribers of specified service.
     * 
     * \return GIMI_OK if message was sent successfully to the hub.
     * GIMI_NOCONNECTION if connection to hub has been lost.
     * GIMI_NOSUBSCRIBERS if there were no subscribers.
     * GIMI_INVALIDTYPE if type did not exist.
     * GIMI_ERROR if some error occurred.
     */
    int sendKeepAliveToSubscribers(const int majorTypeId,
                                   const int minorTypeId);
    
    /**
     * Tries to process data in a replypacket sent to GIMI.
     * (find value of GIMITAG_REPLY)
     * 
     * \return Boolean for success.
     */
    bool processReplyData(int *replyValue,
                          const char *data,
                          const int datalength);
    
    /**
     * Construct a control message.
     *
     * \return True.
     */
    bool makeMessage(BiteStream *outStream,
                     const int msgDataType,
                     const uint32_t msgUid,
                     const int msgActFlags,
                     const int majorTypeId,
                     const int minorTypeId,
                     const int msgId,
                     const int msgCommand,
                     const char* data = NULL,
                     const int datalength = 0);
    
    /**
     * Constructs a basis for all messages from mandatory tags.
     * \warning Optional tags need to be added and stream finalized before use.
     *
     * \return True.
     */
    void makeBaseMessage(BiteStream *outStream,
                         const int msgDataType,
                         const uint32_t msgUid,
                         const int msgActFlag,
                         const int majorTypeId,
                         const int minorTypeId);

    /**
     * Upkeepthread, handles timeouts etc. upkeeping tasks.
     */
    static int upkeepThread(void *);

    /**
     * Processes incoming GIMI-messages that are determined to be data.
     * Messages are parsed open and forwarded to messagebuffers.
     * If necessary, ACK-messages are sent to sender to indicate that message
     * has been successfully received.
     *
     * \return Boolean for success.
     */
    bool processDataMessage(GIMIMessage **msg,
                            BiteStream *stream,
                            bsTag *tag,
                            const uint32_t msgUid,
                            const int msgActFlags,
                            const int msgDataType);

    /**
     * Processes incoming GIMI-messages that are control-packets for GIMI.
     * Messages are parsed open and forwarded for do***Command methods.
     *
     * \return Boolean for success.
     */
    bool processControlMessage(GIMIMessage **msg,
                               BiteStream *stream,
                               bsTag *tag,
                               const uint32_t msgUid,
                               const int msgActFlags,
                               const int msgDataType);

    /**
     * Do commands
     *
     * \return Boolean for success.
     */
    bool doDataCommand(GIMIMessage **msg,
                       const uint32_t msgUid,
                       const int msgActFlags,
                       const int msgCommand);

    bool doSubscribedDataCommand(GIMIMessage **msg,
                                 const uint32_t msgUid,
                                 const int msgActFlags,
                                 const int msgCommand);

    bool doServicesCommand(GIMIMessage **msg,
                           const uint32_t msgUid,
                           const int msgActFlags,
                           const int msgCommand);

    bool doControlCommand(GIMIMessage **msg,
                          const uint32_t msgUid,
                          const int msgActFlags,
                          const int msgCommand);


    // Storage for Service data
    TServiceListArray iServiceDiscoveryEventArray; ///< Event array for online events
    unsigned int iServiceListEventArraySeq; ///< Sequence number; for last event received.
    TServiceListBundleArray iServiceDiscoveryBundleArray; ///< Receiving 


    // Variable
    unsigned int iDefaultConnectorTimeout_ms;

    // Active?
    ActiveStatus activeStatus;

    // Running value for unique id.
    // Protect with idMutex
    uint32_t iUniqueIdentifier;

    int defaultBufferSize;

    // For saving values from settings class
    // Only used when connectToHub is called. 
    std::string hubAddress;
    int hubPort;
    std::string ownHubName;
    
    std::string clientDescription;
    std::string clientLocation;
    gim::version clientVersion;


    // UpkeepThread
    ownThreadHandle handleUpkeepThread;
    
};

}

#endif /*GIMICORE_H_*/
